/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package kpsmart.model.routes;

import java.util.*;
import kpsmart.model.events.CostType;
import org.joda.time.DateTime;
import org.joda.time.Duration;

/**
 * A chained route is a data type that holds a list of CostTypes to represent a
 * route
 *
 * @author Oliver Behncke
 */
public class ChainedRoute {

    private List<CostType> costs;
    private String start;
    private String end;

    // Private constructor use factory method instead
    private ChainedRoute(List<CostType> costs) {
        this.costs = new LinkedList<CostType>(costs);
        this.start = costs.get(0).getFrom();
        this.end = costs.get(costs.size() - 1).getTo();
    }

    /**
     * Get start town of chained route
     *
     * @return start
     */
    public String getStart() {
        return start;
    }

    /**
     * Get final destination of chained route
     *
     * @return end of route
     */
    public String getEnd() {
        return end;
    }

    /**
     * Get a copy of the list of cost types that represent the route
     *
     * @return costTypes
     */
    public List<CostType> getCostTypes() {
        return new ArrayList<CostType>(costs);
    }

    /**
     * Overall duration of a mail item processed on this route. Needs the
     * timestamp of the mail item to calculate the overall duration
     *
     * @param time
     * @return hours as int
     */
    public int duration(long time) {
        int result = 0;
        DateTime date = new DateTime(time);
        for (CostType c : costs) {
            int frequency = c.getFrequency().intValue();
            int duration = c.getDuration().intValue();
            DateTime routeStart = new DateTime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), 12, 0);
            // set the right day of the week
            while (routeStart.getDayOfWeek() != Routes.noForDay(c.getDay())) {
                routeStart = routeStart.plusDays(1);
            }
            // Special treatment for frequency that represent a multiple of a quarter of a week.
            if (frequency >= 42 && (frequency % 42) == 0) {
                if (routeStart.isBefore(date)) {
                    routeStart = routeStart.plusHours(frequency);
                }
            } else {
                // add th frequencies to figure out on which time of the day
                while (routeStart.getDayOfWeek() != date.getDayOfWeek()) {
                    routeStart = routeStart.plusHours(frequency);
                }
                routeStart = new DateTime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), routeStart.getHourOfDay(), 0);
                while (date.getMillis() > routeStart.getMillis()) {
                    routeStart = routeStart.plusHours(frequency);
                }

            }
            result += new Duration(date, routeStart).getStandardHours() + duration;
            date = routeStart.plusHours(duration);
        }
        return result;
    }

    /**
     * Calculate the overall weight limitation of the chained route
     *
     * @return weightLimitation as int
     */
    public int overallWeightlimitation() {
        int result = Integer.MAX_VALUE;
        for (CostType c : costs) {
            if (c.getMaxWeight().intValue() < result) {
                result = c.getMaxWeight().intValue();
            }
        }
        return result;
    }

    /**
     * Calculate the overall volume limitation of the chained route
     *
     * @return volumeLimitation as int
     */
    public int overallVolumelimitation() {
        int result = Integer.MAX_VALUE;
        for (CostType c : costs) {
            if (c.getMaxVolume().intValue() < result) {
                result = c.getMaxVolume().intValue();
            }
        }
        return result;
    }

    /**
     * Overall cost of the route for a given weight and volume
     *
     * @param weight
     * @param volume
     * @return cost as int
     */
    public int overallCost(int weight, int volume) {
        if (weight > overallWeightlimitation()) {
            throw new RuntimeException("Weight bigger than max. weight of Route");
        }
        if (volume > overallVolumelimitation()) {
            throw new RuntimeException("Volume bigger than max. volume of Route");
        }
        int result = 0;
        for (CostType c : costs) {
            result += c.getVolumecost().intValue() * volume + c.getWeightcost().intValue() * weight;
        }
        return result;
    }

    /**
     * Calculate the possible priorities for this route
     *
     * @return list
     */
    public Collection<String> overallPossiblePriorities() {
        String prefix;
        if (Routes.inNewzealand(start) && !Routes.inNewzealand(end)) {
            prefix = "International";
        } else {
            prefix = "Domestic";
        }
        Set<String> priorities = new HashSet<String>(Arrays.asList((prefix + " Air"), (prefix + " Standard")));
        for (CostType c : costs) {
            if (!c.getType().equals("Air")) {
                if (prefix.equals("International") && !Routes.inNewzealand(c.getTo())) {
                    priorities.remove("International Air");
                } else if (prefix.equals("Domestic")) {
                    priorities.remove("Domestic Air");
                }
            }
        }
        return priorities;
    }

    /**
     * Factory method to create a chained route from a list of costs
     *
     * @param costs
     * @return
     */
    static ChainedRoute create(List<CostType> costs) {
        if (costs == null) {
            throw new RuntimeException("Arguments cannot be null");
        }
        if (costs.isEmpty()) {
            throw new RuntimeException("List of CostType cannot be empty");
        }
        return new ChainedRoute(costs);
    }

    @Override
    public String toString() {
        String result = start + "-" + end + " Route: ";
        for (CostType c : costs) {
            result += c.getFrom() + "-" + c.getTo() + ";";
        }
        return result + " Vol.: " + overallVolumelimitation() + " Weig.:" + overallWeightlimitation();
    }
}
